/**
 * 
 */
package za.co.sindi.oauth.core.scanner.impl;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;

import za.co.sindi.oauth.core.annotation.Service;
import za.co.sindi.oauth.core.scanner.AbstractClassScanner;
import za.co.sindi.oauth.core.scanner.ClassScanner;
import za.co.sindi.oauth.core.scanner.filter.impl.AnnotationTypeFilter;
import za.co.sindi.oauth.exception.ScanningException;

/**
 * This class scans Java's classpath, <code>System.getProperty("java.class.path")</code>, for matching classes.
 * 
 * @author Buhake Sindi
 * @since 28 March 2012
 *
 */
public class ClasspathClassScanner extends AbstractClassScanner {

	private static final String PATH_SEPARATOR = File.pathSeparator;
	private static final String FILE_SEPARATOR = File.separator;
	private ClassLoader classLoader;

	public ClasspathClassScanner() {
		this(Thread.currentThread().getContextClassLoader());
	}
	
	/**
	 * @param classLoader
	 */
	public ClasspathClassScanner(ClassLoader classLoader) {		
		this.classLoader = classLoader;
		if (this.classLoader == null) {
			this.classLoader = Thread.currentThread().getContextClassLoader();
		}
		
		if (this.classLoader == null) {
			this.classLoader = this.getClass().getClassLoader();
		}
		
		if (this.classLoader == null) {
			throw new IllegalStateException("Unable to initialize ClassLoader.");
		}
	}

	/* (non-Javadoc)
	 * @see com.neurologic.scanner.ClassScanner#scan(java.lang.String)
	 */
	@Override
	public Set<Class<?>> scan(String basePackageName) throws ScanningException {
		// TODO Auto-generated method stub
		if (basePackageName == null) {
			throw new IllegalArgumentException("Base package name may not be null.");
		}
		
		Set<Class<?>> classes = new LinkedHashSet<Class<?>>();
		try {
			String classpath = System.getProperty("java.class.path");
			if (classpath != null) {
				String[] classpaths = classpath.split(PATH_SEPARATOR);
				for (String path : classpaths) {
					//Scan root path first
					if (logger.isInfoEnabled()) {
						logger.info("Scanning " + path + " folder.");
					}
					
					scan(path, basePackageName, classes);
				}
			}
		} catch (IOException e) {
			// TODO Auto-generated catch block
			throw new ScanningException(e.getLocalizedMessage(), e);
		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			throw new ScanningException(e.getLocalizedMessage(), e);
		}
		
		return Collections.unmodifiableSet(classes);
	}
	
	private void scan(String path, String packageName, Set<Class<?>> classes) throws IOException, ClassNotFoundException {
		File file = new File(path);
		if (!file.exists()) {
			if (logger.isInfoEnabled()) {
				logger.info("Path '" + path + "' doesn't exist. Skipping...");
			}
			
			return;
		}

		if (file.isDirectory()) {
			for (String dir : file.list()) {
				scan(file.getCanonicalPath() + FILE_SEPARATOR + dir, packageName, classes);
			}
		} else {
			if (path.endsWith(JAVA_JAR_EXTENSION)) {
				URL url = new URL("jar", "", -1, file.toURI().toURL().toString() + "!/");
				readJarEntries(url, packageName.replace('.', '/'), classes);
			} else if (path.endsWith(JAVA_CLASS_EXTENSION)) {
				String packagePath = packageName.replace(".", FILE_SEPARATOR);
				if (path.contains(packagePath)) {
					String className = path.substring(path.indexOf(packagePath) + 1, path.indexOf(JAVA_CLASS_EXTENSION)).replace(FILE_SEPARATOR, ".");
					Class<?> clazz = classLoader.loadClass(className);
					if (match(clazz)) {
						if (logger.isInfoEnabled()) {
							logger.info("Found matched class '" + className + "'. Adding to set.");
						}
						classes.add(clazz);
					}
				}
			}
		}
	}
	
	public static void main(String[] args) {
		//System.getProperty("java.class.path").split(";")[0]
		ClassScanner scanner = new ClasspathClassScanner(ClasspathClassScanner.class.getClassLoader());
		scanner.addIncludeFilter(new AnnotationTypeFilter(Service.class));
		try {
			scanner.scan("com.neurologic.action");
		} catch (ScanningException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}
